ReactベースのあたらしいフレームワークRemixをためしてみた
はじめに
こんにちは、CX事業本部MAD事業部の森茂です。
re:Inventを前にAWSの情報も気になるところですが、フロントエンド界隈もReact Conf 2021を前にReact v18 betaをはじめ、Next.js v12やReact Router v6、新しいRoutingライブラリReact Locationのリリースなどなど注目のリリースラッシュが続いているようです。そんな中Reactをベースにした新しいフレームワークであるRemixが本日(2021/11/23日本時間)リリースされました。
Remixとは
RemixはReactRouterの作者でもあるMichael Jackson氏(@mjackson)とRyan Florence氏(@ryanflorence)がつくったReactをベースとした新しいフルスタックなフレームワークです。昨年より「supporter preview」としてライセンス購入者向けにベータ版を公開しながら開発を進めていましたが、先日$300万の資金提供を受け「supporter preview」を終了しOSS化することを発表。日本時間の2021年11月23日6:00にYouTubeライブにてOSSとしてv1.0がリリースが発表されました。
また、つい先日The Testing Torophyの考案者でReact Tesing libraryの作者でもあるKent C. Dodds氏(@kentcdodds )がRemixプロジェクトに参画することが発表されされ、さらに注目を浴びています。
RemixはReactをベースとした他のすでに人気のあるフレームワークのNext.jsやGatsbyとは路線が異なり、SSGなど予めデータを用意して高速化を測る手法はとらず、サーバーサイドレンダリングやブラウザのFetchAPIを活用してデータをやり取りするといった手法をとっているようです。先日リリースされたReact Router v6の機能でもあるNested routesを使いコンポーネント間の関連する部分のみを更新するなど、キャッシュ戦略を駆使したフレームワークになっています。(一部個人的な推測を含みます)
またデプロイ先としてはベンダーロックイン0を謳っており、さまざまな環境で動作することを強みとしているようです。ただ独自のアプリケーション・サーバーが動作することが必要なため(Node.js v14以上)デプロイ環境についてはどこでも動かせるというわけにはいかないかもしれません。最近個人的にも気になっているCloudflare WorkersのKV StorageやDurable Objectsを使って、アプリをエッジにデプロイすることもできるようです。$ arc deploy
でLambda + API Gatewayにもデプロイ可能ですがパッケージサイズが非常に大きいため事前に準備が必要です。
どんな機能をもっているかの概要はフレームワークのトップページを見ていくことで体験することができるので、まずはRemixのサイトを見てスクロールしてみてください。なぜかサイト内にはわかる人にはわかる↑↑↓↓←→←→BAといった記載もあります:)
Remixのチュートリアルをやってみる
実際にどのようなものが作れるのか、早速チュートリアルとして用意されているQuickstartをやってみました。 以下チュートリアルを実際に進めながら気づいたこと感じたことを記載している形となっていますので、実際の画面を体験もしくは見ながら読んでいただけると幸いです。また、リリース直後の情報を読み進めながらのため私自身の理解や解釈が誤っている場合もございますのであらかじめご了承ください。
インストール
早速インストールしていきます。
$ npx create-remix@latest
途中デプロイ先を選ぶ箇所があるのですが、今回はRemix App Serverを選択しています。他の選択肢ではどのようなソースになるかも気になるところです。
ディレクトリ構成
my-remix-app ├── .cache/ ├── README.md ├── app/ ├── build/ ├── node_modules ├── package-lock.json ├── package.json ├── public/ ├── remix.config.js ├── remix.env.d.ts └── tsconfig.json
起動
$ npm run dev
http://localhost:3000 でサンプルアプリケーションが立ち上がありました。
Your First Route
まずはapp/root.tsx
ファイルの編集から。ソースを見る感じCRAで作成したReactアプリケーションでのindex.tsx
とApp.tsx
を足したようなものになるのでしょうか。metaタグやcss、ErrorBoundaryなどをこのファイルで設置しているようです。またサンプルアプリではレイアウトコンポーネントなどもここで用意しているみたいです。
ReactRouter v6の新機能Outletも使われてますね:)
/** * The root module's default export is a component that renders the current * route via the `<Outlet />` component. Think of this as the global layout * component for your app. */ export default function App() { return ( <Document> <Layout> <Outlet /> </Layout> </Document> ); }
リンクを設置してブラウザでアクセスするとちゃんと404のステータスコードが返ってきました。SPAだとページがなくても200を返すことになってしまうので地味にこれはすごい。ステータスコードにあわせたエラーページもuseCatch
というhooksを利用して実現しているようです。
export function CatchBoundary() { let caught = useCatch(); let message; switch (caught.status) { case 401: message = ( <p> Oops! Looks like you tried to visit a page that you do not have access to. </p> ); break; case 404: message = ( <p>Oops! Looks like you tried to visit a page that does not exist.</p> ); break; default: throw new Error(caught.data || caught.statusText); } return ( <Document title={`${caught.status} ${caught.statusText}`}> <Layout> <h1> {caught.status}: {caught.statusText} </h1> {message} </Layout> </Document> ); }
app/route/posts/index.tsx
ファイルを追加。routeディレクトリに配置したファイル名がそのままURLとして利用できる模様。このあたりはNext.jsのpagesディレクトリと同じような扱いになりそうです。
Loading Data
さきほど作成した空のコンポーネントにデータを投入していきます。RemixではuseLoaderData
というhooksを利用してコンポーネントにデータを入れることができるみたいです。Next.jsでいうgetServerSideProps
みたいな感じでしょうか。
loader
として用意したデータが事前にレンダリングされているのも確認できました。
A little refactoring
チュートリアルの通りにリファクタリングしていきます。すでにここでgetPosts()
に集約することでデータソースとしては何でもいけそうな雰囲気を醸し出していますね:)
一箇所ソースの修正、app/post.ts
に移したtype Post
にexport type Post
としておく必要ありです。
Pulling from a data source
ハードコーディングしたデータから出力していた部分をファイルシステムからMarkdownを読み込む形に変更していくようです。このあたりをデータベースとやり取りする形に書き換えればもう立派なアプリケーションとして活用できそうですね。若干Next.jsのチュートリアルと同じような雰囲気になってきました:)
Dynamic Route Params
動的なルートもroutesディレクトリ下に$変数名.tsx
と配置することでparams
から取得できるようです。
import { useLoaderData } from "remix"; import type { LoaderFunction } from "remix"; export let loader: LoaderFunction = async ({ params }) => { return params.slug; }; export default function PostSlug() { let slug = useLoaderData(); return ( <div> <h1>Some Post: {slug}</h1> </div> ); }
Creating Blog Posts
新規投稿用のadminコンポーネントを作成していきます。cssの読み込ませ方がちょっと独特ですね。
import { Link, useLoaderData } from "remix"; import { getPosts } from "~/post"; import type { Post } from "~/post"; import adminStyles from "~/styles/admin.css"; export let links = () => { return [{ rel: "stylesheet", href: adminStyles }]; };
Index Routes
引き続きadminコンポーネントを作成していきます。Outletの使い方も兼ねたチュートリアルになっているみたいです。
Actions
ここからRemixのForm
コンポーネント、useActionData
、useTransition
と立て続けに機能の実装と紹介になっています。
import { Form, redirect } from "remix"; import type { ActionFunction } from "remix"; import { createPost } from "~/post"; export let action: ActionFunction = async ({ request }) => { let formData = await request.formData(); let title = formData.get("title"); let slug = formData.get("slug"); let markdown = formData.get("markdown"); await createPost({ title, slug, markdown }); return redirect("/admin"); };
これだけの記述でformのデータをやり取りできるとは。。。
またuseTransition
を使うとPOSTやGETなどメソッドに合わせた状態なども取得できるようです。
import { useTransition, useActionData, Form, redirect } from "remix"; // ... export default function NewPost() { let errors = useActionData(); let transition = useTransition(); return ( <Form method="post"> {/* ... */} <p> <button type="submit"> {transition.submission ? "Creating..." : "Create Post"} </button> </p> </Form> ); }
Homework
つづいて投稿の編集画面もつくってみようというところでチュートリアルは終わります。
さいごに
今朝リリースされたばかり(執筆時時点)のRemixのチュートリアルを駆け足で体験してみました。QuckstartではRemixの触り部分だけでしたが、ドキュメントやGitHubを見る限り面白そうな機能や実装がたくさんあるようなのでこれからさらに深堀りしていきたいですし、SSGには頼らずにサーバーサイドで快適な速度を実現していこうという試みと、それをベンダーロックインなくフレームワーク側で用意したコンポーネントやhooksで簡単にハンドリングできるようにしている部分など、最近のはやりとはちょっと違う視点のロジックが隠されていそうでわくわくしています。
ちなみに、Quickstartとは別にJokes AppというPrismaでsqliteを利用した本格的なチュートリアルも紹介されているので、そちらもこれから試してみます:)
なお、Jokes AppはCode Sandboxでも早速体験できるようになったようです。